/*************************************************************
*
* CAMERA.CPP
*
* Camera control functions
*************************************************************/
#include <m3d/m3d_stdafx.h>

#include <ap_comm.h>
#include <m3d.h>
#include <camera.h>

// default camera viewport size
static void _camMatrInvert          (m3dMATR *pMat);
static void _camMatrOrthoNormalize  (m3dMATR *pMat);

#define CAM_DEF_VIEWWIN_DX  640.f
#define CAM_DEF_VIEWWIN_DY  480.f

/*************************************************************
* camCAMERA::camCAMERA()
*
* Camera constructor
* Intialize camera fields by default
*************************************************************/
camCAMERA::camCAMERA(void)
{
   index = -1;
   nearPlane = 0.1f;           // near and far clipping planes (=focal distance)
   farPlane  = 10000.f;
   aspectRatioHW = CAM_DEF_VIEWWIN_DY / CAM_DEF_VIEWWIN_DX;

   interest = m3dVZero;

   SetAngleHor(80.f);
/*   
   float       xViewPortOffset;     // viewport offset (in world, i.e. image plane coords [-1..1])
   float       yViewPortOffset;          
   float       dxViewPort;          // viewport Dx, Dy (in world, i.e. image plane coords [-1..1])
   float       dyViewPort;          

   float       xViewWinOffset;      // viewwindow offset (in Screen space)
   float       yViewWinOffset;
   float       dxViewWin;           // viewwindow Dx, Dy (in Screen space, [640 x 480])
   float       dyViewWin;

   float       angleHor;            // camera angles (0-180, i.e. not half-angle)
   float       angleVert;           // camera angles (0-180, i.e. not half-angle)
   float       aspectRatioHW;

   float       ratioDXW2DXViewWin;  // ratio of viewport to viewwindow dimensions
   float       ratioDYW2DYViewWin;  // 
*/

   // set default parameters
   // origin/orientation
   this->matrC2W.Identity();
   this->matrC2W.ElementRC(2, 2) = -1.f;
   AdjustMatrW2C();

   SetViewWindow(0.f, 0.f, CAM_DEF_VIEWWIN_DX, CAM_DEF_VIEWWIN_DY);

   pSplFOV      = NULL;
   pSplRoll     = NULL;    
   pSplPos      = NULL;     
   pSplInterest = NULL;

   return ;
}

/*************************************************************
* camCAMERA::~camCAMERA()
*
* Camera destructor
*************************************************************/
camCAMERA::~camCAMERA (void)
{
   delete pSplFOV;
   delete pSplRoll;    
   delete pSplPos;     
   delete pSplInterest;

   return;
}


/*************************************************************
* camCAMERA::AdjustMatrW2C()
*
* Adjust WCS->CCS matrix after the change in internal matrix
*************************************************************/
void camCAMERA::AdjustMatrW2C (void)
{
   _camMatrOrthoNormalize(&this->matrC2W);
   this->matrW2C      = this->matrC2W;
   _camMatrInvert(&this->matrW2C);
}

/*************************************************************
* camCAMERA::SetViewportDx()
*
* IN: wWidth  - new viewport width
*
* Set camera viewport width (height is not changed)
*************************************************************/
void camCAMERA::SetViewportDx (float wWidth)
{
   m2dPOLY  *pPolyViewport;

   // get camera viewport
   pPolyViewport = &this->polyViewport;

   m3dPolyVertexX(pPolyViewport,0) = -wWidth/2;
   m3dPolyVertexX(pPolyViewport,1) =  wWidth/2;
   m3dPolyVertexX(pPolyViewport,2) =  wWidth/2;
   m3dPolyVertexX(pPolyViewport,3) = -wWidth/2;
   m3dPolyNVert(pPolyViewport) = 4;

   this->xViewPortOffset = -wWidth/2.f;
   this->dxViewPort      =  wWidth;

   // recompute the ratio
   this->ratioDXW2DXViewWin = this->dxViewWin / this->dxViewPort;

   return;
}

/*************************************************************
* camCAMERA::SetViewportDy()
*
* IN: wWidth  - new viewport height
*
* Set camera viewport height (width is not changed)
*************************************************************/
void camCAMERA::SetViewportDy (float wHeight)
{
   m2dPOLY  *pPolyViewport;

   // get camera viewport
   pPolyViewport = &this->polyViewport;

   m3dPolyVertexY(pPolyViewport,0) = -wHeight/2;
   m3dPolyVertexY(pPolyViewport,1) = -wHeight/2;
   m3dPolyVertexY(pPolyViewport,2) =  wHeight/2;
   m3dPolyVertexY(pPolyViewport,3) =  wHeight/2;
   m3dPolyNVert(pPolyViewport) = 4;
   
   this->yViewPortOffset = -wHeight/2.f;
   this->dyViewPort      = wHeight;

   // recompute the ratio
   this->ratioDYW2DYViewWin = this->dyViewWin  / this->dyViewPort;

   return;
}

/*************************************************************
* camCAMERA::RecomputeViewport()
*
* Calculate viewport size basing on current settings
* of nearPlane(=focalLength), angles and aspect ratio
*************************************************************/
void camCAMERA::RecomputeViewport (void)
{
   float    wWidth, wHeight;
   float    angleHor_rad, angleVert_rad;

   angleVert_rad = m3dDeg2Rad(angleVert);
   angleHor_rad  = m3dDeg2Rad(angleHor);

   wWidth  = 2*nearPlane*m3dTan(angleHor_rad /2);
   wHeight = 2*nearPlane*m3dTan(angleVert_rad/2);

   SetViewportDx(wWidth);
   SetViewportDy(wHeight);

   return;
}

/*************************************************************
* camCAMERA::SetAspectRatioHW()
*
* Aspect ratio height / width
*************************************************************/
void camCAMERA::SetAspectRatioHW (float _aspectRatioHW)
{
   aspectRatioHW = _aspectRatioHW;
   RecomputeViewport();
   return;
}

/*************************************************************
* camCAMERA::SetAngleHor()
*
*************************************************************/
void camCAMERA::SetAngleHor (float _angleHor)
{
   angleHor  = _angleHor;
   angleVert = m3dRad2Deg(2.f * (float)m3dATan(m3dTan(m3dDeg2Rad(angleHor/2))*aspectRatioHW));

   RecomputeViewport();
   return;
}
/*************************************************************
* camCAMERA::SetAngleHorVert()
*
*************************************************************/
void  camCAMERA::SetAngleHorVert(float _angleHor, float _angleVert)
{
   angleHor  = _angleHor;
   angleVert = _angleVert;
   RecomputeViewport();
   return;
}

/*************************************************************
* camCAMERA::SetAngleVert()
*
*************************************************************/
void camCAMERA::SetAngleVert (float _angleVert)
{
   angleVert = _angleVert;
   angleHor  = m3dRad2Deg(2.f * (float)m3dATan(m3dTan(m3dDeg2Rad(angleVert/2))/aspectRatioHW));
   RecomputeViewport();
   return;
}

/*************************************************************
* camCAMERA::SetNearPlane()
*
*************************************************************/
void camCAMERA::SetNearPlane (float _nearPlane)
{
   nearPlane = _nearPlane;
   RecomputeViewport();
   return;
}

/*************************************************************
* camCAMERA::SetViewWindow()
*
*************************************************************/
void camCAMERA::SetViewWindow (float xViewWinOffset, float yViewWinOffset,
                               float dxViewWin,      float dyViewWin)
{
   this->xViewWinOffset = xViewWinOffset;
   this->yViewWinOffset = yViewWinOffset;
   this->dxViewWin      = dxViewWin;
   this->dyViewWin      = dyViewWin;
   this->ratioDXW2DXViewWin = dxViewWin / this->dxViewPort;
   this->ratioDYW2DYViewWin = dyViewWin / this->dyViewPort;
   return;
}

/*************************************************************
* camCAMERA::SetViewPort()
*
*************************************************************/
void camCAMERA::SetViewPort (float xViewPortOffset, float yViewPortOffset,
                             float dxViewPort,      float dyViewPort)
{
   m2dPOLY     *pPolyViewport;

   pPolyViewport = &this->polyViewport;

   m3dPolyVertexX(pPolyViewport,0) = xViewPortOffset;
   m3dPolyVertexY(pPolyViewport,0) = yViewPortOffset;
   m3dPolyVertexX(pPolyViewport,1) = xViewPortOffset + dxViewPort;
   m3dPolyVertexY(pPolyViewport,1) = yViewPortOffset;
   m3dPolyVertexX(pPolyViewport,2) = xViewPortOffset + dxViewPort;
   m3dPolyVertexY(pPolyViewport,2) = yViewPortOffset + dyViewPort;
   m3dPolyVertexX(pPolyViewport,3) = xViewPortOffset;
   m3dPolyVertexY(pPolyViewport,3) = yViewPortOffset + dyViewPort;

   this->xViewPortOffset = xViewPortOffset;
   this->yViewPortOffset = yViewPortOffset;
   this->dxViewPort      = dxViewPort;
   this->dyViewPort      = dyViewPort;

   this->ratioDXW2DXViewWin = this->dxViewWin / this->dxViewPort;
   this->ratioDYW2DYViewWin = this->dyViewWin / this->dyViewPort;

   return;
}

/*************************************************************
* _camMatrOrthoNormalize()
*
* Orthonormalize the matrix
* After a number of rotations has been applied to the camera
* matrix it becomes not-orthonormal, that makes it
* inappropriate for simple matrix invert algo, and also
* causes visual artifacts.
* This function is to be called regularly
*************************************************************/
static void _camMatrOrthoNormalize (m3dMATR *pMat)
{
   int   r, c;
   float sum;

   for (r = 0; r < 3; r++ )  {
      sum = 0.f;
      for (c = 0; c < 3; c++ )  {
         sum += m3dPow2(m3dMatrElementRC(pMat,r,c));
      }
      // 0.8 is an arbitrary number > 0
      // normally sum is expected to be very close to 1
      //ASSERT(sum > 0.8f);
      sum = m3dSqrt(sum);

      for (c = 0; c < 3; c++ )  {
         m3dMatrElementRC(pMat,r,c) /= sum;
      }
   }

   return;
}

/*************************************************************
* _camMatrInvert()
*
* Calculate the inverse matrix to an orthoNormal matrix
* The inverse matrix is calculated by simple transposition
* of upper 3x3 matrix and inverse translation.
* Formulas are taken from Watt&Watt, pp. 8-9
*************************************************************/
static void _camMatrInvert (m3dMATR *pMat)
{
   int        r, c;
   m3dMATR    matT, matB;
#ifdef _DEBUG   
   m3dMATR    mat;
#endif

   // construct matrix B
   for (r = 0; r < 3; r++ )  {
      for (c = 0; c < 3; c++ )  {
         m3dMatrElementRC(&matB,r,c) = m3dMatrElementRC(pMat,c,r);
      }

      m3dMatrElementRC(&matB,r,c) = 0.f;
   }

   for (c = 0; c < 3; c++ )  {
      m3dMatrElementRC(&matB,3,c) = 0.f;
   }
   m3dMatrElementRC(&matB,3,3) = 1.f;

   // construct matrix T
   matT.Identity();
   matT.Translate(-pMat->ElementRC(3,0), -pMat->ElementRC(3,1),-pMat->ElementRC(3,2),M3D_REPLACE);
#ifdef _DEBUG   
   // test only - store a copy of a given matrix to check the quality of inversion
   mat = *pMat;
#endif

   m3dMultiplyMatr(&matT, &matB, pMat); // Instead of Copy/Transform %%%

#ifdef _DEBUG   
   // test only 
   mat.Transform(pMat,M3D_PRE);

   // check if we really produce invert matrices
   for (r = 0; r < 4; r++ )  {
      for (c = 0; c < 4; c++ )  {
         float t;

         t = m3dMatrElementRC(&mat,r,c);

         if (r == c )  {
            //ASSERT(m3dFAbs(m3dMatrElementRC(&mat,r,c) - 1.f) < 1000.f*M3D_EPSILON);
         } else {
            //ASSERT(m3dFAbs(m3dMatrElementRC(&mat,r,c)) < 10000*M3D_EPSILON);
         }
      }
   }
#endif
   
   return;
}

/*************************************************************
* camCAMERA::MoveTo()
*
* IN: x, y, z  - desired camera position (in WCS)
*
* Move camera origin to absolute world position
*************************************************************/
void camCAMERA::MoveTo (float x, float y, float z)
{
   m3dMatrElementRC(&this->matrC2W, 3, 0) = x;
   m3dMatrElementRC(&this->matrC2W, 3, 1) = y;
   m3dMatrElementRC(&this->matrC2W, 3, 2) = z;

   AdjustMatrW2C();
   STRONG_ASSERT(matrC2W.Check());
   return;
}

void camCAMERA::MoveTo (m3dV *pos)
{
   MoveTo(pos->x, pos->y, pos->z);
   return;
}

/*************************************************************
* camCAMERA::MoveRelForw()
* camCAMERA::MoveRelSide()
* camCAMERA::MoveRelUp  ()
* camCAMERA::MoveRel    ()
*
* Move camera along the respective local camera axis by 
* the amount 'delta'
* IN: delta - relative move
*************************************************************/
void camCAMERA::MoveRelForw (float delta)
{
   // move the camera along local Z-axis
   matrC2W.Translate(0.f, 0.f, -delta, M3D_PRE);

   AdjustMatrW2C();

   return;
}

void camCAMERA::MoveRelSide (float delta)
{
   // move the camera along local X-axis
   matrC2W.Translate(delta, 0.f, 0.f, M3D_PRE);

   AdjustMatrW2C();

   return;
}

void camCAMERA::MoveRelUp (float delta)
{
   // move the camera along local Y-axis
   matrC2W.Translate(0.f, delta, 0.f, M3D_PRE);

   AdjustMatrW2C();

   return;
}

void camCAMERA::MoveRel (m3dV *vDelta)
{
   MoveRel(vDelta->x, vDelta->y, vDelta->z);
   return;
}

void camCAMERA::MoveRel (float dX, float dY, float dZ)
{
   matrC2W.Translate(dX, dY, dZ, M3D_PRE);

   AdjustMatrW2C();

   return;
}

/*************************************************************
* camCAMERA::TurnRelAroundX()
* camCAMERA::TurnRelAroundY()
* camCAMERA::TurnRelAroundZ()
*
* Turn camera relative to the local camera axes by angle 'phi'
*    TurnAroundY - look left/right
*    TurnAroundX - look up/down
*    TurnAroundZ - swing the head left/right
*************************************************************/
void camCAMERA::TurnRelAroundY (float phi)
{
   // turn the camera along local Y-axis
   this->matrC2W.RotateY(phi, M3D_PRE);

   AdjustMatrW2C();

   return;
}

void camCAMERA::TurnRelAroundX (float phi)
{
   // turn the camera along local X-axis
   this->matrC2W.RotateX(phi, M3D_PRE);

   AdjustMatrW2C();

   return;
}

void camCAMERA::TurnRelAroundZ (float phi)
{
   // turn the camera along local Z-axis
   this->matrC2W.RotateZ(phi, M3D_PRE);

   AdjustMatrW2C();

   return;
}

/**********************************************************************
* camCAMERA::TurnAroundVert()
*
* IN: phi  - rotate angle
**********************************************************************/
void camCAMERA::TurnAroundVert (float phi)
{
   float  xOffs, yOffs, zOffs;

   xOffs = m3dMatrElementRC(&this->matrC2W, 3, 0);
   yOffs = m3dMatrElementRC(&this->matrC2W, 3, 1);
   zOffs = m3dMatrElementRC(&this->matrC2W, 3, 2);
   m3dMatrElementRC(&this->matrC2W, 3, 0) = 0.f;
   m3dMatrElementRC(&this->matrC2W, 3, 1) = 0.f;
   m3dMatrElementRC(&this->matrC2W, 3, 2) = 0.f;

   // turn the camera along global Y-axis
   this->matrC2W.RotateY(phi, M3D_POST);

   m3dMatrElementRC(&this->matrC2W, 3, 0) = xOffs;
   m3dMatrElementRC(&this->matrC2W, 3, 1) = yOffs;
   m3dMatrElementRC(&this->matrC2W, 3, 2) = zOffs;
   AdjustMatrW2C();
   return;
}

/*************************************************************
* camCAMERA::ResetRotations()
*
* Reset camera rotations. Align camera axes along X/Y/-Z WCS axis
*************************************************************/
void camCAMERA::ResetRotations (void)
{
   m3dV org;

   GetOrigin(&org);

   // reset camera rotations/shifts
   this->matrC2W.Identity();
   m3dMatrElementRC(&this->matrC2W,2,2) = -1.f;

   // restore camera position in WCS
   MoveTo(org.x, org.y, org.z);
   return;
}

/*************************************************************
* camCAMERA::ResetVertRotation()
*
* Reset camera vertical rotations (camera looks in plane)
*************************************************************/
void camCAMERA::ResetVertRotation (void)
{
   m3dV vLookAt;

   GetLookAt(&vLookAt);
   vLookAt.y = 0.f;
   if (!m3dIsZero(m3dLengthVector(&vLookAt)) )  {
      m3dNormalize(&vLookAt);
   } else {
      vLookAt.x = vLookAt.y = 0.f;
      vLookAt.z = 1.f;
   }

   SetLookAt(&vLookAt);

   return;
}

/*************************************************************
* camCAMERA::SetInterest()
*
* IN: pInterest - point of interest (in WCS)
*
* Set LookAt vector to point in the direction to pInterest
*************************************************************/
void camCAMERA::SetInterest (m3dV *pInterest)
{
   m3dV  org, vAt;

   GetOrigin(&org);

	m3dSubtractVector(pInterest, &org, &vAt);

   // points are to be different
   ASSERT(m3dLengthVector(&vAt) > M3D_EPSILON);

   SetLookAt(&vAt);

   this->interest = *pInterest;
	return;
}

/*************************************************************
* camCAMERA::SetLookAt()
*
* Set LookAt vector in the direction of pLookAt and set 
* camera vertically
* IN  : pLookAt - camera direction vector
* NOTE: pLookAt can be not normalized
*************************************************************/
void camCAMERA::SetLookAt (m3dV *pLookAt)
{
   m3dV			org, vUp, vRight, vTmp, vLookAt;

   vLookAt = *pLookAt;
   GetOrigin(&org);

	m3dNormalize(&vLookAt);
	vUp = m3dVUnitY;

	m3dScaleVector(&vLookAt, m3dDotProduct(&vLookAt, &vUp), &vTmp);
	m3dSubtractVector(&vUp, &vTmp, &vUp);
   if (!_m3dNormalize(&vUp)) {
      // set camera vertically
      vUp = m3dVUnitX;
   }
	m3dCrossProduct(&vLookAt, &vUp, &vRight);

   SetParameters(&org, &vUp, &vRight, &vLookAt);
   return;
}

/*************************************************************
* camCAMERA::TransformPoint()
*
* IN:  pW   - point in WCS
* OUT: pC   - transformed point in CCS
*
* Transform point from WCS to CCS
*************************************************************/
void camCAMERA::TransformPoint (const m3dV *pW, m3dV *pC) const
{
   // points are to be different
   ASSERT(pC != pW);

   this->matrW2C.TransformPoint(pW, pC);
   
   return;
}

/*************************************************************
* camCAMERA::TransformPointC2W()
*
* IN:  pC   - point in CCS
* OUT: pW   - transformed point in WCS
*
* Transform point from CCS to WCS
*************************************************************/
void camCAMERA::TransformPointC2W (const m3dV *pC, m3dV *pW) const
{
   // points are to be different
   ASSERT(pC != pW);

   this->matrC2W.TransformPoint(pC, pW);
   
   return;
}

/*************************************************************
* camCAMERA::TransformVector()
*
* IN:  pW   - vector in WCS
* OUT: pC   - transformed vector in CCS
*
* Transform vector from WCS to CCS
*************************************************************/
void camCAMERA::TransformVector (const m3dV *pW, m3dV *pC) const
{
   // points are to be different
   ASSERT(pC != pW);

   this->matrW2C.TransformVector(pW, pC);
   
   return;
}

/*************************************************************
* camCAMERA::TransformVectorC2W()
*
* IN:  pW   - vector in CCS
* OUT: pC   - transformed vector in WCS
*
* Transform vector from CCS to WCS
*************************************************************/
void camCAMERA::TransformVectorC2W (const m3dV *pC, m3dV *pW) const
{
   // points are to be different
   ASSERT(pC != pW);

   this->matrC2W.TransformVector(pC, pW);
   
   return;
}

/***********************************************************************
* camCAMERA::TransformMatrC2W()
*
* IN: pMatr - transformation matrix
*     oper  - order of operation
*
* Transform C2W camera matrix by pMatr and adjust W2C matrix accordingly
***********************************************************************/
void camCAMERA::TransformMatrC2W (m3dMATR *pMatr, int oper)
{
   m3dMATR tmpMatr;

   switch (oper) {
      case M3D_PRE:
         m3dMultiplyMatr(pMatr, &this->matrC2W, &tmpMatr);
         break;
      case M3D_POST:
         m3dMultiplyMatr(&this->matrC2W, pMatr, &tmpMatr);
         break;
      case M3D_REPLACE:
         tmpMatr = *pMatr;
         break;
      default:
         ASSERT(FALSE); // no other flags supported
   }
   this->matrC2W = tmpMatr;
   AdjustMatrW2C();
   return;
}

/*************************************************************
* camCAMERA::GetParameters()
*
* OUT: pOrg   - camera origin
*      pUp    - camera UP      vector
*      pRight - camera RIGHT   vector
*      pForw  - camera FORWARD vector
*************************************************************/
void camCAMERA::GetParameters (m3dV * __RESTRICT pOrg, m3dV * __RESTRICT pUp, m3dV * __RESTRICT pRight, m3dV * __RESTRICT pForw)
{
   if (pOrg) { 
      GetOrigin(pOrg); 
   }
   if (pUp) { 
      GetVUp(pUp); 
   }
   if (pRight) { 
      GetVRight(pRight); 
   }
   if (pForw) { 
      GetVForw(pForw); 
   }
   return;
}

/***********************************************************
* camCAMERA::SetParameters()
*
* IN:  pOrg   - new origin
*      pUp    - new 'Up'      vector
*      pRight - new 'Right'   vector
*      pForw  - new 'Forward' vector
*
* NOTE: the function doesn't check if (pUp, pRight, pForw) is
* a right orthonormal basis. You should do it yourself if needed
***********************************************************/
void camCAMERA::SetParameters (m3dV *pOrg, m3dV *pUp, m3dV *pRight, m3dV *pForw)
{
#ifdef _DEBUG
   m3dV    vTmp;
   m3dCrossProduct(pUp, pRight, &vTmp);
   ASSERT(m3dIsZero_A(m3dDist(&vTmp, pForw),     1000*M3D_EPSILON) || // for regular camera
          m3dIsZero_A(m3dDist(&vTmp, pForw)-2.f, 1000*M3D_EPSILON));  // for mirror camera
#endif

   // get pointer to camera C2W matrix
   m3dMATR *pMatrC2W = &this->matrC2W;

   pMatrC2W->Identity();
   // fill camera C2W matrix
	m3dMatrElementRC(pMatrC2W, 0, 0) = pRight->x;
	m3dMatrElementRC(pMatrC2W, 1, 0) = pUp->x   ;
	m3dMatrElementRC(pMatrC2W, 2, 0) = pForw->x ;
	m3dMatrElementRC(pMatrC2W, 0, 1) = pRight->y;
	m3dMatrElementRC(pMatrC2W, 1, 1) = pUp->y   ;
	m3dMatrElementRC(pMatrC2W, 2, 1) = pForw->y ;
	m3dMatrElementRC(pMatrC2W, 0, 2) = pRight->z;
	m3dMatrElementRC(pMatrC2W, 1, 2) = pUp->z   ;
	m3dMatrElementRC(pMatrC2W, 2, 2) = pForw->z ;
   m3dMatrElementRC(pMatrC2W,3,0)   = pOrg->x;
   m3dMatrElementRC(pMatrC2W,3,1)   = pOrg->y;
   m3dMatrElementRC(pMatrC2W,3,2)   = pOrg->z;

   // adjust W2C matrix
   AdjustMatrW2C();

   STRONG_ASSERT(_m3dCheckValid(pOrg));
   STRONG_ASSERT(_m3dCheckValid(pUp));
   STRONG_ASSERT(_m3dCheckValid(pRight));
   STRONG_ASSERT(_m3dCheckValid(pForw));
   STRONG_ASSERT(pMatrC2W->Check());
   return;
}

/******************************************************************
* camCAMERA::GetViewwindowRayDir()
*
* Converts window coordinates to ray direction (in WCS)
* IN:  pCam   - camera
*      x, y   - window coordinates 
* OUT: dir    - ray direction
*               from camera origin throw center of pixel
* NOTE: If you need a ray 
*******************************************************************/
void camCAMERA::GetViewWindowRayDir (float x, float y, m3dV *dir)
{
   m3dV  p;

   p.x = SxToWx(x);
   p.y = SyToWy(y);
   p.z = nearPlane;

   this->matrC2W.TransformVector(&p, dir);
   return;
}
/******************************************************************
* camCAMERA::GetViewFrustumEdgeDir ()
*
* Converts window coordinates to ray direction (in WCS)
* IN:  pCam   - camera
*      x, y   - window coordinates 
* OUT: dir    - ray direction
*               from camera origin throw center of pixel
* NOTE: If you need a ray 
*******************************************************************/

void camCAMERA::GetViewFrustumEdgeDir (int i, m3dV *dir) const
{
   m3dV  p;

   p.x = polyViewport[i].x;
   p.y = polyViewport[i].y;
   p.z = nearPlane;

   matrC2W.TransformVector(&p, dir);
   m3dNormalize(dir);
   return;
}

/******************************************************************
* camCAMERA::GetViewWindowXY()
*
* Converts ray direction (WCS) to window coordinates
* IN : dir    - ray direction from camera origin
* OUT: x, y   - window coordinates
* RET: FALSE if this ray doesn't belong viewwindow
*******************************************************************/
BOOL camCAMERA::GetViewWindowXY (m3dV *dir, float *x, float *y)
{
   m3dV  p;
   float scale;

   matrW2C.TransformVector(dir, &p);

   // Interpolate camera coordinates
   if (m3dIsZero(p.z)) {
      return FALSE;
   }
   scale = nearPlane / p.z;
   _m3dScaleVector(&p, scale);

   *x = WxToSx(p.x);
   *y = WyToSy(p.y);

   if (!IsValidXViewWin(*x) || !IsValidYViewWin(*y)) {
      return FALSE;
   }
   return TRUE;
}
/******************************************************************
* camCAMERA::Animate ()
*
* Animate camera
* IN : frameNmb - current frame number
* OUT: pAnim    - animated camera
* RET: none
*******************************************************************/
void  camCAMERA::Animate (float frameNmb, int animState, camCAMERA *pAnim)
{

   if (pAnim == NULL) {
      pAnim = this;
   }

   if (_IsState(animState, CAM_ANIM_FOV)) {
      // aspect ratio, must be set before hor or vert angles
      float aspectHW = GetAspectRatioHW();
      pAnim->SetAspectRatioHW(aspectHW);

      // vertical angle of view
      float fow;
      if (pSplFOV) {
         pSplFOV->GetValue(frameNmb, &fow);
      } else {
         fow = GetAngleHor();
      }
      pAnim->SetAngleHor(fow);
   }

   m3dV org, up, right, forw;
   this->GetParameters(&org, &up, &right, &forw);
   pAnim->SetParameters(&org, &up, &right, &forw);

   if (_IsState(animState, CAM_ANIM_POS)) {
      if (pSplPos) {
         pSplPos->GetValue(frameNmb, &org);
         pAnim->MoveTo(&org);
      }
   }
   if (_IsState(animState, CAM_ANIM_INTERES)) {
      if (pSplInterest) {
         pSplInterest->GetValue(frameNmb, &org);
         pAnim->SetInterest(&org);
      }
   }
   if (_IsState(animState, CAM_ANIM_ROLL)) {
      if (pSplRoll) {
         float angle;
         pSplRoll->GetValue(frameNmb, &angle);
         pAnim->TurnRelAroundZ(angle);
      }
   }
   return;
}
/******************************************************************
* camCAMERA::Mirror ()
*
*******************************************************************/
void camCAMERA::Mirror (m3dV *planePoint, m3dV *planeNorm)
{
   m3dV        camOrg, camUp, camRight, camForw;
   m3dV        camOrgNew, camUpNew, camRightNew, camForwNew;

   GetParameters(&camOrg, &camUp, &camRight, &camForw);

   m3dMirrorPointPlane(planePoint, planeNorm, &camOrg,   &camOrgNew);
   m3dMirrorPointPlane(&m3dVZero,  planeNorm, &camUp,    &camUpNew);
   m3dMirrorPointPlane(&m3dVZero,  planeNorm, &camRight, &camRightNew);
   _m3dNegateVector(&camRightNew);
   m3dMirrorPointPlane(&m3dVZero,  planeNorm, &camForw,  &camForwNew);

   SetParameters(&camOrgNew, &camUpNew, &camRightNew, &camForwNew);

   SetViewPort(GetXViewPortOffset(), GetYViewPortOffset(), GetDXViewPort(), GetDYViewPort());
   SetViewWindow(GetXViewWinOffset() + GetDXViewWin(), GetYViewWinOffset(),  -GetDXViewWin(),  GetDYViewWin());
   return;
}

/******************************************************************
* camCAMERA::Mirror ()
*
* same as C2W but left oriented for physics purposes
*******************************************************************/
void camCAMERA::GetMatrC2WLeft  (m3dMATR *pMatr)
{
   m3dV org;
   GetOrigin(&org);
   m3dV vForw;
   GetLookAt(&vForw);
   m3dV vUp;
   GetVUp(&vUp);

   pMatr->MakeLCS2WCS(&org, NULL, &vUp, &vForw);
   return;
}

//
// End-of-file CAMERA.CPP
//
